﻿%define pg.RootNamespace ParserGenerator
%define pg.SyntacticAnalysisNamespace ParserGenerator.Languages.Let
%define pg.SourceContextNamespace ParserGenerator.Input
%define pg.OutputNamespace ParserGenerator.Output

%token Number : BigInteger
%token Identifier : Name

%define pg.indented False

%define pg.datatype.namespace ParserGenerator.Languages.Let
%define pg.datatype.usings (
    System,
    System.Collections.Generic,
    System.Numerics,
    ParserGenerator.Collections,
    ParserGenerator.Input,
    ParserGenerator.LexicalAnalysis,
)

%define pg.parser.classname ProgramParser
%define pg.parser.namespace ParserGenerator.Languages.Let
%define pg.parser.usings (
    System,
    System.Numerics,
    ParserGenerator.Collections,
    ParserGenerator.Input,
    ParserGenerator.LexicalAnalysis,
)

Program = (
    body : Expression,
    context! : SourceContext?
)

Parameter = (
    name : Name,
    `type`! : Type?,
    context! : SourceContext?
)

ConditionActionPair = (
    condition : Expression,
    action : Expression,
    context! : SourceContext?
)

NameValuePair = (
    name : Name,
    value : Expression,
    context! : SourceContext?
)

LetrecProcedure = (
    name : Name,
    parameters : [Parameter],
    resultType! : Type?,
    body : Expression,
    context! : SourceContext?
)

Expression:
    Const (value : BigInteger)
    Minus (operand : Expression)
    Sum (left : Expression, right : Expression)
    Diff (left : Expression, right : Expression)
    Prod (left : Expression, right : Expression)
    Quot (left : Expression, right : Expression)
    Equal (left : Expression, right : Expression)
    Greater (left : Expression, right : Expression)
    Less (left : Expression, right : Expression)
    IsZero (operand : Expression)
    Cons (left : Expression, right : Expression)
    Car (operand : Expression)
    Cdr (operand : Expression)
    IsNull (operand : Expression)
    EmptyList
    List (elements : [Expression]+)
    `True`
    `False`
    If (test : Expression, body : Expression, orElse : Expression)
    Cond (branches : [ConditionActionPair]+)
    Print (operand : Expression)
    Var (name : Name)
    Let (isSequential : bool, bindings : [NameValuePair], body : Expression)
    Letrec (procedures : [LetrecProcedure], body : Expression)
    Unpack (names : [Name], value : Expression, body : Expression)
    Proc (parameters : [Parameter], body : Expression)
    Call (operator : Expression, operands : [Expression])
    attributes! (context! : SourceContext?)

Value:
    Num (val : BigInteger)
    Bool (val : bool)
    EmptyList
    Pair (first : Value, second : Value)
    Proc (parameters : [Name], body : Expression, env! : Environment)

Type:
    Int
    Bool
    TypeVar (number : int)
    List (element : Type)
    Func (arguments : [Type], result : Type)

IFreeVariableChecker.Check(n : Expression) -> HashSet[string]
IExpressionEvaluator.Evaluate(n : Expression) -> Value
ITypeChecker.Check(n : Expression)

%%

Program as Program:
    Expression => Program.New($1, @@)

Parameter as Parameter:
    Identifier ':' Type => Parameter.New($1, $3, @@)
    Identifier => Parameter.New($1, None, @@)

ConditionActionPair as ConditionActionPair:
    Expression '==>' Expression => ConditionActionPair.New($1, $3, @@)

NameValuePair as NameValuePair:
    Identifier '=' Expression => NameValuePair.New($1, $3, @@)

LetrecProcedure as LetrecProcedure:
    Identifier '(' Parameter % ',' ')' ':' Type '=' Expression => LetrecProcedure.New($1, $3, $6, $8, @@)
    Identifier '(' Parameter % ',' ')' '=' Expression => LetrecProcedure.New($1, $3, None, $6, @@)

Expression as Expression:
    Number => Expression.NewConst($1, @@)
    'minus' '(' Expression ')' => Expression.NewMinus($3, @@)
    '+' '(' Expression ',' Expression ')' => Expression.NewSum($3, $5, @@)
    '-' '(' Expression ',' Expression ')' => Expression.NewDiff($3, $5, @@)
    '*' '(' Expression ',' Expression ')' => Expression.NewProd($3, $5, @@)
    '/' '(' Expression ',' Expression ')' => Expression.NewQuot($3, $5, @@)
    'equal?' '(' Expression ',' Expression ')' => Expression.NewEqual($3, $5, @@)
    'greater?' '(' Expression ',' Expression ')' => Expression.NewGreater($3, $5, @@)
    'less?' '(' Expression ',' Expression ')' => Expression.NewLess($3, $5, @@)
    'zero?' '(' Expression ')' => Expression.NewIsZero($3, @@)
    'cons' '(' Expression ',' Expression ')' => Expression.NewCons($3, $5, @@)
    'car' '(' Expression ')' => Expression.NewCar($3, @@)
    'cdr' '(' Expression ')' => Expression.NewCdr($3, @@)
    'null?' '(' Expression ')' => Expression.NewIsNull($3, @@)
    'emptylist' => Expression.NewEmptyList(@@)
    'list' '(' Expression %+ ',' ')' => Expression.NewList($3, @@)
    'true' => Expression.NewTrue(@@)
    'false' => Expression.NewFalse(@@)
    'if' Expression 'then' Expression 'else' Expression => Expression.NewIf($2, $4, $6, @@)
    'cond' ConditionActionPair+ 'end' => Expression.NewCond($2, @@)
    'print' '(' Expression ')' => Expression.NewPrint($3, @@)
    Identifier => Expression.NewVar($1, @@)
    'let' NameValuePair* 'in' Expression => Expression.NewLet(False, $2, $4, @@)
    'let*' NameValuePair* 'in' Expression => Expression.NewLet(True, $2, $4, @@)
    'letrec' LetrecProcedure* 'in' Expression => Expression.NewLetrec($2, $4, @@)
    'unpack' Identifier* '=' Expression 'in' Expression => Expression.NewUnpack($2, $4, $6, @@)
    'proc' '(' Parameter % ',' ')' Expression => Expression.NewProc($3, $5, @@)
    '(' Expression Expression* ')' => Expression.NewCall($2, $3, @@)

PrimaryType as Type:
    'int' => Type.NewInt()
    'bool' => Type.NewBool()
    '(' Type ')' => $2
    '[' Type ']' => Type.NewList($2)

ArgumentTypes as [Type]:
    PrimaryType %+ '*' => $1
    '(' ')' => default([Type])

Type as Type:
    PrimaryType => $1
    ArgumentTypes '->' Type => Type.NewFunc($1, $3)    

%%

lower  = ['a'-'z']
upper  = ['A'-'Z']
digit  = ['0'-'9']
letter = lower | upper | '_'
name   = letter (letter | digit)*

nl     = [ '\n' '\v' '\f' '\r' '\x85' '\u2028' '\u2029' ]
ws     = [ '\t' ' ' '\xa0' '\u2000'-'\u200a' '\u202f' '\u205f' '\u3000' ]+

<*>:
    '-'? digit+ =>: ScanNumberValue(); yield Number
    name =>: ScanIdentifierValue(); yield Identifier
    nl =>: pass
    ws =>: pass
